/*******************************************************************************
 * Copyright (c) 2008, 2009 Tapestry IDE Developers
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Tapestry IDE Developers - initial API and implementation
 *******************************************************************************/
package com.googlecode.tapestry.ide.core.model.resources;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.CoreException;

import com.googlecode.tapestry.ide.core.TapestryCoreUtils;
import com.googlecode.tapestry.ide.internal.core.LogUtils;
import com.googlecode.tapestry.ide.internal.core.model.resources.ITapestryResourceChangeEvents;

/**
 * Implementation of {@link IResourceChangeListener} which detects modifications
 * to Tapestry projects and their resources (add/remove Tapestry nature,
 * open/close and delete).
 * <p>
 * An implementation of {@link ITapestryResourceChangeEvents} has to be
 * provided, which defines the call-backs for the different events.
 */
public class TapestryResourceChangeListener implements IResourceChangeListener {

	/** The Constant LISTENER_FLAGS. */
	public static final int LISTENER_FLAGS = IResourceChangeEvent.PRE_CLOSE
			| IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.PRE_BUILD
			| IResourceChangeEvent.POST_BUILD
			| IResourceChangeEvent.PRE_REFRESH;

	/** The Constant VISITOR_FLAGS. */
	private static final int VISITOR_FLAGS = IResourceDelta.ADDED
			| IResourceDelta.CHANGED | IResourceDelta.REMOVED;

	/** The events. */
	private ITapestryResourceChangeEvents events;

	/**
	 * Instantiates a new tapestry resource change listener.
	 * 
	 * @param events
	 *            the events
	 */
	public TapestryResourceChangeListener(ITapestryResourceChangeEvents events) {
		super();
		this.events = events;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org
	 * .eclipse.core.resources.IResourceChangeEvent)
	 */
	public void resourceChanged(IResourceChangeEvent event) {
		if (event.getSource() instanceof IWorkspace) {
			int eventType = event.getType();
			switch (eventType) {
			case IResourceChangeEvent.PRE_CLOSE:
				IProject closedProject = (IProject) event.getResource();
				if (TapestryCoreUtils.isTapestryProject(closedProject)) {
					events.projectClosed(closedProject, eventType);
				}
				break;

			case IResourceChangeEvent.PRE_DELETE:
				IProject openedProject = (IProject) event.getResource();
				if (TapestryCoreUtils.isTapestryProject(openedProject)) {
					events.projectDeleted(openedProject, eventType);
				}
				break;

			case IResourceChangeEvent.PRE_BUILD:
			case IResourceChangeEvent.POST_BUILD:
			case IResourceChangeEvent.PRE_REFRESH:
				IResourceDelta delta = event.getDelta();
				if (delta != null) {
					try {
						delta.accept(getVisitor(eventType), VISITOR_FLAGS);
					} catch (CoreException e) {
						LogUtils.log("Error while traversing "
								+ "resource change delta", e);
					}
				}
				break;
			}
		} else if (event.getSource() instanceof IProject) {
			int eventType = event.getType();
			switch (eventType) {
			case IResourceChangeEvent.PRE_CLOSE:
				IProject closedProject = (IProject) event.getSource();
				if (TapestryCoreUtils.isTapestryProject(closedProject)) {
					events.projectClosed(closedProject, eventType);
				}
				break;

			case IResourceChangeEvent.PRE_DELETE:
				IProject openedProject = (IProject) event.getSource();
				if (TapestryCoreUtils.isTapestryProject(openedProject)) {
					events.projectDeleted(openedProject, eventType);
				}
				break;

			case IResourceChangeEvent.PRE_BUILD:
			case IResourceChangeEvent.POST_BUILD:
				IResourceDelta delta = event.getDelta();
				if (delta != null) {
					try {
						delta.accept(getVisitor(eventType), VISITOR_FLAGS);
					} catch (CoreException e) {
						LogUtils.log("Error while traversing "
								+ "resource change delta", e);
					}
				}
				break;
			}
		}

	}

	/**
	 * Gets the visitor.
	 * 
	 * @param eventType
	 *            the event type
	 * 
	 * @return the visitor
	 */
	protected IResourceDeltaVisitor getVisitor(int eventType) {
		return new TapestryResourceVisitor(eventType);
	}

	/**
	 * Internal resource delta visitor.
	 */
	protected class TapestryResourceVisitor implements IResourceDeltaVisitor {

		/** The event type. */
		protected int eventType;

		/**
		 * Instantiates a new tapestry resource visitor.
		 * 
		 * @param eventType
		 *            the event type
		 */
		public TapestryResourceVisitor(int eventType) {
			this.eventType = eventType;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse
		 * .core.resources.IResourceDelta)
		 */
		public final boolean visit(IResourceDelta delta) throws CoreException {
			IResource resource = delta.getResource();
			switch (delta.getKind()) {
			case IResourceDelta.ADDED:
				return resourceAdded(resource);

			case IResourceDelta.OPEN:
				return resourceOpened(resource);

			case IResourceDelta.CHANGED:
				return resourceChanged(resource, delta.getFlags());

			case IResourceDelta.REMOVED:
				return resourceRemoved(resource);
			}
			return true;
		}

		/**
		 * Resource added.
		 * 
		 * @param resource
		 *            the resource
		 * 
		 * @return true, if successful
		 */
		protected boolean resourceAdded(IResource resource) {
			if (resource instanceof IProject) {
				if (TapestryCoreUtils.isTapestryProject(resource)) {
					events.projectAdded((IProject) resource, eventType);
				}
				return false;
			}
			if (resource instanceof IFile) {
				IFile file = (IFile) resource;
				events.resourceAdded(file, eventType);
				return false;
			}
			return true;
		}

		/**
		 * Resource opened.
		 * 
		 * @param resource
		 *            the resource
		 * 
		 * @return true, if successful
		 */
		protected boolean resourceOpened(IResource resource) {
			if (resource instanceof IProject) {
				if (TapestryCoreUtils.isTapestryProject(resource)) {
					events.projectOpened((IProject) resource, eventType);
				}
				return false;
			}
			return true;
		}

		/**
		 * Resource changed.
		 * 
		 * @param resource
		 *            the resource
		 * @param flags
		 *            the flags
		 * 
		 * @return true, if successful
		 */
		protected boolean resourceChanged(IResource resource, int flags) {
			if (resource instanceof IProject) {
				if ((flags & IResourceDelta.OPEN) != 0) {
					if (TapestryCoreUtils.isTapestryProject(resource)) {
						events.projectOpened((IProject) resource, eventType);
					}
					return false;
				} else if ((flags & IResourceDelta.DESCRIPTION) != 0) {
					IProject project = (IProject) resource;
					if (TapestryCoreUtils.isTapestryProject(project)) {
						if (!events.isTapestryProject((IProject) resource,
								eventType)) {
							events.tapestryNatureAdded(project, eventType);
						}
					} else if (events.isTapestryProject(project, eventType)) {
						events.tapestryNatureRemoved(project, eventType);
					}
					return false;
				}
			}
			return true;
		}

		/**
		 * Resource removed.
		 * 
		 * @param resource
		 *            the resource
		 * 
		 * @return true, if successful
		 */
		protected boolean resourceRemoved(IResource resource) {
			if (resource instanceof IProject) {
				if (TapestryCoreUtils.isTapestryProject(resource)) {
					events.projectDeleted((IProject) resource, eventType);
				}
				return false;
			}
			if (resource instanceof IFile) {
				IFile file = (IFile) resource;
				events.resourceRemoved(file, eventType);
				return false;
			}
			return true;
		}
	}
}
